using System;
using dnAnalytics.LinearAlgebra;
using NUnit.Framework;

namespace dnAnalytics.Tests.LinearAlgebra
{
    [TestFixture]
    public class SparseMatrixTests : MatrixTests
    {
        public override Matrix GetMatrix(int order)
        {
            return new SparseMatrix(order);
        }

        public override Matrix GetMatrix(int m, int n)
        {
            return new SparseMatrix(m, n);
        }

        public override Matrix GetMatrix(double[,] data)
        {
            return new SparseMatrix(data);
        }

        public override Vector GetVector(int size)
        {
            return new SparseVector(size);
        }

        public override Vector GetVector(double[] data)
        {
            return new SparseVector(data);
        }

        // BUG FIX TEST:
        // Bug behavior: 
        // I get an IndexOutOfRangeException when trying to get the submatrix of a sparse matrix (in special cases).
        // Here is the sample code:
        // DelimitedMatrixReader reader = new DelimitedMatrixReader(',');
        // Matrix matrix = reader.ReadMatrix("matrix-bug.txt", MatrixType.Sparse);
        // Range range = new Range(82, 138);
        // Matrix subMatrix = matrix.SubMatrix(range, range);
        // Bug reason: The sparse matrix did not check if the returned columns were within the 
        // range from the lower side, thereby returning negative column indices.

        [Test]
        public void SubMatrixTest()
        {
            Matrix matrix = GetMatrix(square);

            int rowLength = matrix.Rows - 2;
            int columnLength = matrix.Columns - 2;
            
            Matrix subMatrix = matrix.GetSubMatrix(1, rowLength, 1, columnLength);

            for (int i = 1, ii = 0; i <= rowLength; i++, ii++)
            {
                for (int j = 1, jj = 0; j <= columnLength; j++, jj++)
                {
                    Assert.AreEqual(matrix[i, j], subMatrix[ii, jj]);
                }
            }
        }

        [Test]
        public void SparseOperatorMultiplyMatrixScalar()
        {
            foreach (double[,] data in matrices)
            {
                SparseMatrix SparseMatrix = new SparseMatrix(data);
                SparseMatrix result = SparseMatrix * .1234;
                for (int i = 0; i < SparseMatrix.Rows; i++)
                {
                    for (int j = 0; j < SparseMatrix.Columns; j++)
                    {
                        Assert.AreEqual(SparseMatrix[i, j] * .1234, result[i, j], Constants.AcceptableError);
                    }
                }
            }
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void SparseOperatorMultiplyMatrixScalarNull()
        {
            SparseMatrix SparseMatrix = null;
            SparseMatrix result = SparseMatrix * .1234;
        }

        [Test]
        public void SparseOperatorMultiplyScalarMatrix()
        {
            SparseMatrix SparseMatrix = new SparseMatrix(square);
            SparseMatrix result = .1234 * SparseMatrix;
            for (int i = 0; i < SparseMatrix.Rows; i++)
            {
                for (int j = 0; j < SparseMatrix.Columns; j++)
                {
                    Assert.AreEqual(square[i, j] * .1234, result[i, j], Constants.AcceptableError);
                }
            }

            SparseMatrix = new SparseMatrix(wide);
            result = .1234 * SparseMatrix;
            for (int i = 0; i < SparseMatrix.Rows; i++)
            {
                for (int j = 0; j < SparseMatrix.Columns; j++)
                {
                    Assert.AreEqual(wide[i, j] * .1234, result[i, j], Constants.AcceptableError);
                }
            }

            SparseMatrix = new SparseMatrix(tall);
            result = .1234 * SparseMatrix;
            for (int i = 0; i < SparseMatrix.Rows; i++)
            {
                for (int j = 0; j < SparseMatrix.Columns; j++)
                {
                    Assert.AreEqual(tall[i, j] * .1234, result[i, j], Constants.AcceptableError);
                }
            }
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void SparseOperatorMultiplyScalarMatrixNull()
        {
            SparseMatrix SparseMatrix = null;
            SparseMatrix result = .1234 * SparseMatrix;
        }

        [Test]
        public void SparseOperatorMultiplyMatrixMatrix()
        {
            SparseMatrix matrix1 = new SparseMatrix(square);
            SparseMatrix matrix2 = new SparseMatrix(square);
            SparseMatrix result = matrix1 * matrix2;

            Assert.AreEqual(3, result.Rows);
            Assert.AreEqual(3, result.Columns);
            Assert.AreEqual(15.73, result[0, 0], Constants.AcceptableError);
            Assert.AreEqual(-18.15, result[0, 1], Constants.AcceptableError);
            Assert.AreEqual(-22.99, result[0, 2], Constants.AcceptableError);
            Assert.AreEqual(-9.68, result[1, 0], Constants.AcceptableError);
            Assert.AreEqual(13.31, result[1, 1], Constants.AcceptableError);
            Assert.AreEqual(16.94, result[1, 2], Constants.AcceptableError);
            Assert.AreEqual(-24.2, result[2, 0], Constants.AcceptableError);
            Assert.AreEqual(52.03, result[2, 1], Constants.AcceptableError);
            Assert.AreEqual(70.17999999999999, result[2, 2], Constants.AcceptableError);

            matrix1 = new SparseMatrix(wide);
            matrix2 = new SparseMatrix(tall);
            result = matrix1 * matrix2;

            Assert.AreEqual(2, result.Rows);
            Assert.AreEqual(2, result.Columns);
            Assert.AreEqual(15.73, result[0, 0], Constants.AcceptableError);
            Assert.AreEqual(-18.15, result[0, 1], Constants.AcceptableError);
            Assert.AreEqual(-9.68, result[1, 0], Constants.AcceptableError);
            Assert.AreEqual(13.31, result[1, 1], Constants.AcceptableError);

            matrix1 = new SparseMatrix(tall);
            matrix2 = new SparseMatrix(wide);
            result = matrix1 * matrix2;

            Assert.AreEqual(3, result.Rows);
            Assert.AreEqual(3, result.Columns);
            Assert.AreEqual(1.21, result[0, 0], Constants.AcceptableError);
            Assert.AreEqual(0, result[0, 1], Constants.AcceptableError);
            Assert.AreEqual(-1.21, result[0, 2], Constants.AcceptableError);
            Assert.AreEqual(0, result[1, 0], Constants.AcceptableError);
            Assert.AreEqual(1.21, result[1, 1], Constants.AcceptableError);
            Assert.AreEqual(2.42, result[1, 2], Constants.AcceptableError);
            Assert.AreEqual(4.84, result[2, 0], Constants.AcceptableError);
            Assert.AreEqual(15.73, result[2, 1], Constants.AcceptableError);
            Assert.AreEqual(26.62, result[2, 2], Constants.AcceptableError);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void SparseOperatotMulitplyMatrixNullMatrix()
        {
            SparseMatrix matrix1 = null;
            SparseMatrix matrix2 = new SparseMatrix(square);
            SparseMatrix result = matrix1 * matrix2;
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void SparseOperatotMulitplyMatrixMatrixNull()
        {
            SparseMatrix matrix1 = new SparseMatrix(square);
            SparseMatrix matrix2 = null;
            SparseMatrix result = matrix1 * matrix2;
        }

        [Test]
        [ExpectedException(typeof(NotConformableException))]
        public void SparseOperatotMulitplyMatrixMatrixNotConformable()
        {
            SparseMatrix matrix1 = new SparseMatrix(tall);
            SparseMatrix matrix2 = new SparseMatrix(tall);
            SparseMatrix result = matrix1 * matrix2;
        }

        [Test]
        public void SparseOperatorMultiplyMatrixVector()
        {
            SparseMatrix SparseMatrix = new SparseMatrix(square);
            SparseVector SparseVector = new SparseVector(new double[] { 1.1, 2.2, 3.3 });
            SparseVector result = SparseMatrix * SparseVector;
            Assert.AreEqual(SparseMatrix.Rows, result.Count);
            Assert.AreEqual(-16.94, result[0], Constants.AcceptableError);
            Assert.AreEqual(9.68, result[1], Constants.AcceptableError);
            Assert.AreEqual(29.04, result[2], Constants.AcceptableError);

            SparseMatrix = new SparseMatrix(wide);
            SparseVector = new SparseVector(new double[] { 1.1, 2.2, 3.3 });
            result = SparseMatrix * SparseVector;
            Assert.AreEqual(SparseMatrix.Rows, result.Count);
            Assert.AreEqual(-16.94, result[0], Constants.AcceptableError);
            Assert.AreEqual(9.68, result[1], Constants.AcceptableError);

            SparseMatrix = new SparseMatrix(tall);
            SparseVector = new SparseVector(new double[] { 1.1, 2.2 });
            result = SparseMatrix * SparseVector;
            Assert.AreEqual(SparseMatrix.Rows, result.Count);
            Assert.AreEqual(-6.05, result[0], Constants.AcceptableError);
            Assert.AreEqual(2.42, result[1], Constants.AcceptableError);
            Assert.AreEqual(7.26, result[2], Constants.AcceptableError);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void SparseOperatorMultiplyMatrixNullVector()
        {
            SparseMatrix SparseMatrix = null;
            SparseVector SparseVector = new SparseVector(new double[] { 1.1, 2.2, 3.3 });
            SparseVector result = SparseMatrix * SparseVector;
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void SparseOperatorMultiplyMatrixVectorNull()
        {
            SparseMatrix SparseMatrix = new SparseMatrix(square);
            SparseVector SparseVector = null;
            SparseVector result = SparseMatrix * SparseVector;
        }

        [Test]
        [ExpectedException(typeof(NotConformableException))]
        public void SparseOperatorMultiplyMatrixVectorNotConformable()
        {
            SparseMatrix SparseMatrix = new SparseMatrix(square);
            SparseVector SparseVector = new SparseVector(new double[] { 1.1, 2.2 });
            SparseVector result = SparseMatrix * SparseVector;
        }

        [Test]
        public void SparseOperatorMultiplyVectorMatrix()
        {
            SparseMatrix SparseMatrix = new SparseMatrix(square);
            SparseVector SparseVector = new SparseVector(new double[] { 1.1, 2.2, 3.3 });
            SparseVector result = SparseVector * SparseMatrix;
            Assert.AreEqual(SparseMatrix.Columns, result.Count);
            Assert.AreEqual(-15.73, result[0], Constants.AcceptableError);
            Assert.AreEqual(18.15, result[1], Constants.AcceptableError);
            Assert.AreEqual(22.99, result[2], Constants.AcceptableError);

            SparseMatrix = new SparseMatrix(wide);
            SparseVector = new SparseVector(new double[] { 1.1, 2.2 });
            result = SparseVector * SparseMatrix;
            Assert.AreEqual(SparseMatrix.Columns, result.Count);
            Assert.AreEqual(-1.21, result[0], Constants.AcceptableError);
            Assert.AreEqual(0, result[1], Constants.AcceptableError);
            Assert.AreEqual(1.21, result[2], Constants.AcceptableError);

            SparseMatrix = new SparseMatrix(tall);
            SparseVector = new SparseVector(new double[] { 1.1, 2.2, 3.3 });
            result = SparseVector * SparseMatrix;
            Assert.AreEqual(SparseMatrix.Columns, result.Count);
            Assert.AreEqual(-15.73, result[0], Constants.AcceptableError);
            Assert.AreEqual(18.15, result[1], Constants.AcceptableError);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void SparseOperatorMultiplyVectorMatrixNull()
        {
            SparseMatrix SparseMatrix = null;
            SparseVector SparseVector = new SparseVector(new double[] { 1.1, 2.2, 3.3 });
            SparseVector result = SparseVector * SparseMatrix;
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void SparseOperatorMultiplyVectorNullMatrix()
        {
            SparseMatrix SparseMatrix = new SparseMatrix(square);
            SparseVector SparseVector = null;
            SparseVector result = SparseVector * SparseMatrix;
        }

        [Test]
        [ExpectedException(typeof(NotConformableException))]
        public void SparseOperatorMultiplyVectorMatrixNotConformable()
        {
            SparseMatrix SparseMatrix = new SparseMatrix(square);
            SparseVector SparseVector = new SparseVector(new double[] { 1.1, 2.2 });
            SparseVector result = SparseVector * SparseMatrix;
        }
    }
}